/* Copyright (c) Microsoft Corporation.
   Licensed under the MIT License. */

/***************************************************************************
	Author: ShonK
	Project: Kauai
	Copyright (c) Microsoft Corporation

	Graphical video object implementation.

***************************************************************************/
#include "frame.h"
#ifdef WIN
#include "mciavi.h"
#endif //WIN
ASSERTNAME

RTCLASS(GVID)
RTCLASS(GVDS)
RTCLASS(GVDW)

BEGIN_CMD_MAP_BASE(GVDS)
END_CMD_MAP(&GVDS::FCmdAll, pvNil, kgrfcmmAll)

const long kcmhlGvds = kswMin;	//put videos at the head of the list


/***************************************************************************
	Create a video - either an HWND based one, or just a video stream,
	depending on fHwndBased. pgobBase is assumed to be valid for the life
	of the video.
***************************************************************************/
PGVID GVID::PgvidNew(PFNI pfni, PGOB pgobBase, bool fHwndBased, long hid)
{
	AssertPo(pfni, ffniFile);
	AssertPo(pgobBase, 0);

	if (fHwndBased)
		return GVDW::PgvdwNew(pfni, pgobBase, hid);
	return GVDS::PgvdsNew(pfni, pgobBase, hid);
}


/***************************************************************************
	Constructor for a generic video.
***************************************************************************/
GVID::GVID(long hid) : GVID_PAR(hid)
{
	AssertBaseThis(0);
}


/***************************************************************************
	Constructor for video stream class.
***************************************************************************/
GVDS::GVDS(long hid) : GVDS_PAR(hid)
{
	AssertBaseThis(0);

#ifdef WIN
	AVIFileInit();
#endif //WIN
}


/***************************************************************************
	Destructor for video stream class.
***************************************************************************/
GVDS::~GVDS(void)
{
	AssertBaseThis(0);

#ifdef WIN
	if (hNil != _hdd)
		DrawDibClose(_hdd);
	if (pvNil != _pavig)
		AVIStreamGetFrameClose(_pavig);
	if (pvNil != _pavis)
		AVIStreamRelease(_pavis);
	if (pvNil != _pavif)
		AVIFileRelease(_pavif);

	AVIFileExit();
#endif //WIN
}


/***************************************************************************
	Initialize a video stream object.
***************************************************************************/
bool GVDS::_FInit(PFNI pfni, PGOB pgobBase)
{
	AssertBaseThis(0);
	AssertPo(pfni, ffniFile);
	AssertPo(pgobBase, 0);

#ifdef WIN
	STN stn;
	AVIFILEINFO afi;

	_pgobBase = pgobBase;
	pfni->GetStnPath(&stn);
	if (0 != AVIFileOpen(&_pavif, stn.Psz(),
			OF_READ | OF_SHARE_DENY_WRITE, pvNil))
		{
		_pavif = pvNil;
		goto LFail;
		}

	if (0 != AVIFileGetStream(_pavif, &_pavis, streamtypeVIDEO, 0))
		{
		_pavis = pvNil;
		goto LFail;
		}

	if (pvNil == (_pavig = AVIStreamGetFrameOpen(_pavis, pvNil)))
		goto LFail;

	if (0 != AVIFileInfo(_pavif, &afi, size(afi)))
		goto LFail;

	_dxp = afi.dwWidth;
	_dyp = afi.dwHeight;

	if (0 > (_nfrMac = AVIStreamLength(_pavis)))
		goto LFail;

	if (0 > (_dnfr = AVIStreamStart(_pavis)))
		goto LFail;

	if (hNil == (_hdd = DrawDibOpen()))
		{
LFail:
		PushErc(ercCantOpenVideo);
		return fFalse;
		}
	_nfrCur = 0;
	_nfrMarked = -1;
	return fTrue;
#else //!WIN
	RawRtn();
	return fFalse;
#endif //!WIN
}


/***************************************************************************
	Create a new video stream object.
***************************************************************************/
PGVDS GVDS::PgvdsNew(PFNI pfni, PGOB pgobBase, long hid)
{
	AssertPo(pfni, ffniFile);
	PGVDS pgvds;

	if (hid == hidNil)
		hid = CMH::HidUnique();

	if (pvNil == (pgvds = NewObj GVDS(hid)))
		return pvNil;

	if (!pgvds->_FInit(pfni, pgobBase))
		{
		ReleasePpo(&pgvds);
		return pvNil;
		}

	return pgvds;
}


/***************************************************************************
	Return the number of frames in the video.
***************************************************************************/
long GVDS::NfrMac(void)
{
	AssertThis(0);
	return _nfrMac;
}


/***************************************************************************
	Return the current frame of the video.
***************************************************************************/
long GVDS::NfrCur(void)
{
	AssertThis(0);
	return _nfrCur;
}


/***************************************************************************
	Advance to a particular frame.  If we are playing, stop playing.  This
	only changes internal state and doesn't mark anything.
***************************************************************************/
void GVDS::GotoNfr(long nfr)
{
	AssertThis(0);
	AssertIn(nfr, 0, _nfrMac);

	Stop();
	_nfrCur = nfr;
}


/***************************************************************************
	Return whether or not the video is playing.
***************************************************************************/
bool GVDS::FPlaying(void)
{
	AssertThis(0);
	return _fPlaying;
}


/***************************************************************************
	Start playing at the current frame.  This assumes the gob is valid
	until the video is stopped or nuked.  The gob should call this video's
	Draw method in its Draw method.
***************************************************************************/
bool GVDS::FPlay(RC *prc)
{
	AssertThis(0);
	AssertNilOrVarMem(prc);

	Stop();
	if (!vpcex->FAddCmh(this, kcmhlGvds, kgrfcmmAll))
		return fFalse;

	SetRcPlay(prc);
	_fPlaying = fTrue;

#ifdef WIN
	_tsPlay = TsCurrent() - AVIStreamSampleToTime(_pavis, _nfrCur + _dnfr);
#endif //WIN

	return fTrue;
}


/***************************************************************************
	Set the rectangle to play into.
***************************************************************************/
void GVDS::SetRcPlay(RC *prc)
{
	AssertThis(0);
	AssertNilOrVarMem(prc);

	if (pvNil == prc)
		_rcPlay.Set(0, 0, _dxp, _dyp);
	else
		_rcPlay = *prc;
}


/***************************************************************************
	Stop playing.
***************************************************************************/
void GVDS::Stop(void)
{
	AssertThis(0);

	vpcex->RemoveCmh(this, kcmhlGvds);
	_fPlaying = fFalse;
}


/***************************************************************************
	Intercepts all commands, so we get to play our movie no matter what.
***************************************************************************/
bool GVDS::FCmdAll(PCMD pcmd)
{
	AssertThis(0);
	AssertVarMem(pcmd);

	if (!_fPlaying)
		{
		Stop();
		return fFalse;
		}

	// update _nfrCur
#ifdef WIN
	_nfrCur = AVIStreamTimeToSample(_pavis, TsCurrent() - _tsPlay) - _dnfr;
	if (_nfrCur >= _nfrMac)
		_nfrCur = _nfrMac - 1;
	else if (_nfrCur < 0)
		_nfrCur = 0;
#endif //WIN

	if (_nfrCur != _nfrMarked)
		{
		_pgobBase->InvalRc(&_rcPlay, kginMark);
		_nfrMarked = _nfrCur;
		}
	if (_nfrCur >= _nfrMac - 1)
		Stop();

	return fFalse;
}


/***************************************************************************
	Call this to draw the current state of the video image.
***************************************************************************/
void GVDS::Draw(PGNV pgnv, RC *prc)
{
	AssertThis(0);
	AssertPo(pgnv, 0);
	AssertVarMem(prc);
	RC rc;

#ifdef WIN
	BITMAPINFOHEADER *pbi;

	if (pvNil == (pbi = (BITMAPINFOHEADER *)AVIStreamGetFrame(_pavig,
			_nfrCur + _dnfr)))
		{
		return;
		}

	pgnv->DrawDib(_hdd, pbi, prc);
#endif //WIN
}


/***************************************************************************
	Get the normal rectangle for the movie (top-left at (0, 0)).
***************************************************************************/
void GVDS::GetRc(RC *prc)
{
	AssertThis(0);
	AssertVarMem(prc);

	prc->Set(0, 0, _dxp, _dyp);
}


#ifdef DEBUG
/***************************************************************************
	Assert the validity of a GVDS.
***************************************************************************/
void GVDS::AssertValid(ulong grf)
{
	GVDS_PAR::AssertValid(0);
	AssertPo(_pgobBase, 0);
	//REVIEW shonk: fill in GVDS::AssertValid
}
#endif //DEBUG


/***************************************************************************
	Create a new video window.
***************************************************************************/
PGVDW GVDW::PgvdwNew(PFNI pfni, PGOB pgobBase, long hid)
{
	AssertPo(pfni, ffniFile);
	PGVDW pgvdw;

	if (hid == hidNil)
		hid = CMH::HidUnique();

	if (pvNil == (pgvdw = NewObj GVDW(hid)))
		return pvNil;

	if (!pgvdw->_FInit(pfni, pgobBase))
		{
		ReleasePpo(&pgvdw);
		return pvNil;
		}

	return pgvdw;
}


/***************************************************************************
	Constructor for a video window.
***************************************************************************/
GVDW::GVDW(long hid) : GVDW_PAR(hid)
{
	AssertBaseThis(0);
}


/***************************************************************************
	Destructor for a video window.
***************************************************************************/
GVDW::~GVDW(void)
{
	AssertBaseThis(0);

#ifdef WIN
	if (_fDeviceOpen)
		{
		MCI_GENERIC_PARMS mci;
		PSNDV psndv;

		mciSendCommand(_lwDevice, MCI_CLOSE, MCI_WAIT, (long)&mci);
		if (pvNil != vpsndm &&
				pvNil != (psndv = vpsndm->PsndvFromCtg(kctgWave)))
			{
			psndv->Suspend(fFalse);
			}
		}
#endif //WIN
}


/***************************************************************************
	Initialize the GVDW.
***************************************************************************/
bool GVDW::_FInit(PFNI pfni, PGOB pgobBase)
{
	AssertPo(pfni, ffniFile);
	AssertPo(pgobBase, 0);

	_pgobBase = pgobBase;

#ifdef WIN
	MCI_ANIM_OPEN_PARMS mciOpen;
	MCI_STATUS_PARMS mciStatus;
	MCI_ANIM_RECT_PARMS mciRect;
	STN stn;
	PSNDV psndv;

	pfni->GetStnPath(&stn);

	ClearPb(&mciOpen, size(mciOpen));
	mciOpen.lpstrDeviceType = PszLit("avivideo");
	mciOpen.lpstrElementName = stn.Psz();
	mciOpen.dwStyle = WS_CHILD | WS_CLIPSIBLINGS | WS_DISABLED;
	mciOpen.hWndParent = _pgobBase->HwndContainer();
	if (0 != mciSendCommand(0, MCI_OPEN,
			MCI_OPEN_TYPE | MCI_OPEN_ELEMENT |
				MCI_ANIM_OPEN_PARENT | MCI_ANIM_OPEN_WS,
			(long)&mciOpen))
		{
		goto LFail;
		}
	_lwDevice = mciOpen.wDeviceID;
	_fDeviceOpen = fTrue;
	if (pvNil != vpsndm &&
			pvNil != (psndv = vpsndm->PsndvFromCtg(kctgWave)))
		{
		psndv->Suspend(fTrue);
		}

	// get the hwnd
	ClearPb(&mciStatus, size(mciStatus));
	// REVIEW shonk: mmsystem.h defines MCI_ANIM_STATUS_HWND as 0x00004003,
	// which doesn't give us the hwnd. 4001 does!
	mciStatus.dwItem = 0x00004001;
	if (0 != mciSendCommand(_lwDevice, MCI_STATUS, MCI_STATUS_ITEM,
			(long)&mciStatus))
		{
		goto LFail;
		}
	_hwndMovie = (HWND)mciStatus.dwReturn;

	// get the length
	ClearPb(&mciStatus, size(mciStatus));
	mciStatus.dwItem = MCI_STATUS_LENGTH;
	mciStatus.dwTrack = 1;
	if (0 != mciSendCommand(_lwDevice, MCI_STATUS, MCI_STATUS_ITEM,
			(long)&mciStatus))
		{
		goto LFail;
		}
	_nfrMac = mciStatus.dwReturn;

	// get the rectangle
	if (0 != mciSendCommand(_lwDevice, MCI_WHERE, MCI_ANIM_WHERE_SOURCE,
			(long)&mciRect))
		{
		goto LFail;
		}
	_rcPlay = (RC)mciRect.rc;
	_dxp = _rcPlay.Dxp();
	_dyp = _rcPlay.Dyp();

	mciSendCommand(_lwDevice, MCI_REALIZE, MCI_ANIM_REALIZE_BKGD, 0);
	_cactPal = vcactRealize;

	return fTrue;

LFail:
#endif //WIN

#ifdef MAC
	RawRtn(); //REVIEW shonk: Mac: implement GVDW::_FInit
#endif //MAC

	PushErc(ercCantOpenVideo);
	return fFalse;
}


/***************************************************************************
	Return the number of frames in the video.
***************************************************************************/
long GVDW::NfrMac(void)
{
	AssertThis(0);

	return _nfrMac;
}


/***************************************************************************
	Return the current frame of the video.
***************************************************************************/
long GVDW::NfrCur(void)
{
	AssertThis(0);

#ifdef WIN
	MCI_STATUS_PARMS mciStatus;

	// get the position
	ClearPb(&mciStatus, size(mciStatus));
	mciStatus.dwItem = MCI_STATUS_POSITION;
	mciStatus.dwTrack = 1;
	if (0 != mciSendCommand(_lwDevice, MCI_STATUS, MCI_STATUS_ITEM,
			(long)&mciStatus))
		{
		Warn("getting position failed");
		return 0;
		}
	return mciStatus.dwReturn;
#endif //WIN

#ifdef MAC
	RawRtn(); //REVIEW shonk: Mac: implement GVDW::NfrCur
	return 0;
#endif //MAC
}


/***************************************************************************
	Advance to a particular frame.  If we are playing, stop playing.  This
	only changes internal state and doesn't mark anything.
***************************************************************************/
void GVDW::GotoNfr(long nfr)
{
	AssertThis(0);
	AssertIn(nfr, 0, _nfrMac);

#ifdef WIN
	MCI_SEEK_PARMS mciSeek;

	ClearPb(&mciSeek, size(mciSeek));
	mciSeek.dwTo = nfr;
	if (0 != mciSendCommand(_lwDevice, MCI_SEEK, MCI_TO,
			(long)&mciSeek))
		{
		Warn("seeking failed");
		}
#endif //WIN

#ifdef MAC
	RawRtn(); //REVIEW shonk: Mac: implement GVDW::GotoNfr
#endif //MAC

}


/***************************************************************************
	Return whether or not the video is playing.
***************************************************************************/
bool GVDW::FPlaying(void)
{
	AssertThis(0);

	if (!_fPlaying)
		return fFalse;

#ifdef WIN
	MCI_STATUS_PARMS mciStatus;

	// get the mode
	ClearPb(&mciStatus, size(mciStatus));
	mciStatus.dwItem = MCI_STATUS_MODE;
	mciStatus.dwTrack = 1;
	if (0 == mciSendCommand(_lwDevice, MCI_STATUS, MCI_STATUS_ITEM,
			(long)&mciStatus) &&
		(MCI_MODE_STOP == mciStatus.dwReturn ||
			MCI_MODE_PAUSE == mciStatus.dwReturn))
		{
		_fPlaying = fFalse;
		}
#endif //WIN

#ifdef MAC
	RawRtn(); //REVIEW shonk: Mac: implement GVDW::NfrCur
#endif //MAC

	return _fPlaying;
}


/***************************************************************************
	Start playing at the current frame.  This assumes the gob is valid
	until the video is stopped or nuked.  The gob should call this video's
	Draw method in its Draw method.
***************************************************************************/
bool GVDW::FPlay(RC *prc)
{
	AssertThis(0);
	AssertNilOrVarMem(prc);

	Stop();

#ifdef WIN
	MCI_ANIM_PLAY_PARMS mciPlay;

	// get the play rectangle
	SetRcPlay(prc);

	// position the hwnd
	_SetRc();

	// start the movie playing
	ClearPb(&mciPlay, size(mciPlay));
	if (0 != mciSendCommand(_lwDevice, MCI_PLAY, MCI_MCIAVI_PLAY_WINDOW,
			(long)&mciPlay))
		{
		return fFalse;
		}
	_fPlaying = fTrue;

	return fTrue;
#endif //WIN

#ifdef MAC
	RawRtn(); //REVIEW shonk: Mac: implement GVDW::NfrCur
	return fFalse;
#endif //MAC
}


/***************************************************************************
	Set the rectangle to play into.
***************************************************************************/
void GVDW::SetRcPlay(RC *prc)
{
	AssertThis(0);
	AssertNilOrVarMem(prc);

	if (pvNil == prc)
		_rcPlay.Set(0, 0, _dxp, _dyp);
	else
		_rcPlay = *prc;
}


/***************************************************************************
	Stop playing.
***************************************************************************/
void GVDW::Stop(void)
{
	AssertThis(0);

	if (!_fPlaying)
		return;

#ifdef WIN
	MCI_GENERIC_PARMS mciPause;

	ClearPb(&mciPause, size(mciPause));
	mciSendCommand(_lwDevice, MCI_PAUSE, 0, (long)&mciPause);
#endif //WIN

#ifdef MAC
	RawRtn(); //REVIEW shonk: Mac: implement GVDW::Stop
#endif //MAC
	_fPlaying = fFalse;
}


/***************************************************************************
	Call this to draw the current state of the video image.
***************************************************************************/
void GVDW::Draw(PGNV pgnv, RC *prc)
{
	AssertThis(0);
	AssertPo(pgnv, 0);
	AssertVarMem(prc);

	_SetRc();
}


/***************************************************************************
	Position the hwnd associated with the video to match the GOB's position.
***************************************************************************/
void GVDW::_SetRc(void)
{
	AssertThis(0);
	RC rcGob, rc;

	_pgobBase->GetRc(&rcGob, cooHwnd);
	rc = _rcPlay;
	rc.Offset(rcGob.xpLeft, rcGob.ypTop);
	if (_rc != rc || !_fVisible)
		{
#ifdef WIN
		MoveWindow(_hwndMovie, rc.xpLeft, rc.ypTop,
			rc.Dxp(), rc.Dyp(), fTrue);
		if (!_fVisible)
			{
			MCI_ANIM_WINDOW_PARMS mciWindow;

			// show the playback window
			ClearPb(&mciWindow, size(mciWindow));
			mciWindow.nCmdShow = SW_SHOW;
			mciSendCommand(_lwDevice, MCI_WINDOW, MCI_ANIM_WINDOW_STATE,
				(long)&mciWindow);
			_fVisible = fTrue;
			}
#endif //WIN

#ifdef MAC
		RawRtn(); //REVIEW shonk: Mac: implement GVDW::_SetRc
#endif //MAC
		_rc = rc;
		}

	if (_cactPal != vcactRealize)
		{
#ifdef WIN
		mciSendCommand(_lwDevice, MCI_REALIZE, MCI_ANIM_REALIZE_BKGD, 0);
#endif //WIN
#ifdef MAC
		RawRtn(); //REVIEW shonk: Mac: implement GVDW::_SetRc
#endif //MAC
		_cactPal = vcactRealize;
		}
}


/***************************************************************************
	Get the normal rectangle for the movie (top-left at (0, 0)).
***************************************************************************/
void GVDW::GetRc(RC *prc)
{
	AssertThis(0);
	AssertVarMem(prc);

	prc->Set(0, 0, _dxp, _dyp);
}


#ifdef DEBUG
/***************************************************************************
	Assert the validity of a GVDW.
***************************************************************************/
void GVDW::AssertValid(ulong grf)
{
	GVDW_PAR::AssertValid(0);
	Assert(_hwndMovie != hNil, 0);
	AssertPo(_pgobBase, 0);
}
#endif //DEBUG
